﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Battles.Atomics;
using MentalAlchemy.Atomics;

namespace Battles.BattleEngine
{
	[Serializable]
	public class BattleEngine
	{
		/// <summary>
		/// Win matrix for 4 classes: Archer, Cavalry, Heavy Infantry, Light Infantry.
		/// </summary>
		public static float[,] winMatrix = {{0.5f, 0.2f, 0.3f, 0.6f},
										    {0.8f, 0.5f, 0.3f, 0.5f},
										    {0.7f, 0.7f, 0.5f, 0.6f},
										    {0.4f, 0.4f, 0.4f, 0.5f}};

		public static float GetArmyStrength (Army army)
		{
			var str = 0f;
			var factor = 1f;
			if (army.Archers.Count > 0)
			{
				factor = Utils.GetTotalHealth(army.Archers) / (army.Archers.Count * Warrior.DEFAULT_HEALTH);
				str += GetTypeStrength(WarriorType.Archer) * army.Archers.Count * factor;
			}
			if (army.Cavalry.Count > 0)
			{
				factor = Utils.GetTotalHealth(army.Cavalry) / (army.Cavalry.Count * Warrior.DEFAULT_HEALTH);
				str += GetTypeStrength(WarriorType.Cavalry) * army.Cavalry.Count * factor;
			}
			if (army.HeavyInf.Count > 0)
			{
				factor = Utils.GetTotalHealth(army.HeavyInf) / (army.HeavyInf.Count * Warrior.DEFAULT_HEALTH);
				str += GetTypeStrength(WarriorType.HeavyInfantry) * army.HeavyInf.Count * factor;
			}
			if (army.LightInf.Count > 0)
			{
				factor = Utils.GetTotalHealth(army.LightInf) / (army.LightInf.Count * Warrior.DEFAULT_HEALTH);
				str += GetTypeStrength(WarriorType.LightInfantry) * army.LightInf.Count * factor;
			}
			return str;
		}

		public static float GetTypeStrength (WarriorType type)
		{
			float[] row;
			switch (type)
			{
				case WarriorType.Archer:
					// get sum of the first row of [winMatrix].
					row = MatrixMath.GetRow(winMatrix, 0);
					break;
				case WarriorType.Cavalry:
					// get sum of the second row of [winMatrix].
					row = MatrixMath.GetRow(winMatrix, 1);
					break;
				case WarriorType.HeavyInfantry:
					// get sum of the third row of [winMatrix].
					row = MatrixMath.GetRow(winMatrix, 2);
					break;
				case WarriorType.LightInfantry:
					// get sum of the forth row of [winMatrix].
					row = MatrixMath.GetRow(winMatrix, 3);
					break;
				default:
					throw new Exception("[GetTypeStrength]: Unknown warrior type (" + type + ")");
			}
			return VectorMath.Sum(row);
		}

		public static float GetWinProbability (WarriorType type1, WarriorType type2)
		{
			int idx = 0;
			switch (type1)
			{
				case WarriorType.Archer:
					idx = 0;
					break;
				case WarriorType.Cavalry:
					idx = 1;
					break;
				case WarriorType.HeavyInfantry:
					idx = 2;
					break;
				case WarriorType.LightInfantry:
					idx = 3;
					break;
			}
			switch (type2)
			{
				case WarriorType.Archer:
					return winMatrix[idx, 0];
				case WarriorType.Cavalry:
					return winMatrix[idx, 1];
				case WarriorType.HeavyInfantry:
					return winMatrix[idx, 2];
				case WarriorType.LightInfantry:
					return winMatrix[idx, 3];
			}
			throw new Exception("[GetWinProbability]: Unknown warrior type (" + type1 + ")");
		}

		/// <summary>
		/// Simulates battle between two armies.
		/// </summary>
		/// <param name="army1"></param>
		/// <param name="army2"></param>
		/// <returns>1 if 1st army is victorious and -1 if 2nd army wins.</returns>
		public static BattleResult Battle (Army army1, Army army2)
		{
			var w1 = army1.Warriors;	// all army1 warriors.
			var w2 = army2.Warriors;	// all army1 warriors.

			var res = new BattleResult();
			int rounds = Math.Max(w1.Count, w2.Count);	// to enable preference for larger army.
			for (int i=0; i<rounds; ++i)
			{
				if (w1.Count == 0 || w2.Count == 0) break;

				// pick two random warriors from each army.
				var rnd1 = w1[ContextRandom.Next(w1.Count)];
				var rnd2 = w2[ContextRandom.Next(w2.Count)];

				if (rnd1.Health > 0 && rnd2.Health > 0)
				{
					var resTemp = Battle(rnd1, rnd2);
					res.Army1Casualties += resTemp.Army1Casualties;
					res.Army2Casualties += resTemp.Army2Casualties;
				}
				else
				{
					if (rnd1.Health <= 0)
					{
						army1.Remove(rnd1);
						w1 = army1.Warriors;
						if (w1.Count == 0) break;
					}
					if (rnd2.Health <= 0)
					{
						army2.Remove(rnd2);
						w2 = army2.Warriors;
						if (w2.Count == 0) break;
					}
				}
			}

			var resHealth1 = Utils.GetTotalHealth(w1);
			var resHealth2 = Utils.GetTotalHealth(w2);
			res.Army1Wins = resHealth1 > resHealth2;

			return res;
		}

		/// <summary>
		/// Defines outcome of the battle between two warriors.
		/// See the formula for calculation of casualties in the code.
		/// </summary>
		/// <param name="w1"></param>
		/// <param name="w2"></param>
		/// <returns></returns>
		public static BattleResult Battle (Warrior w1, Warrior w2)
		{
			var str1 = GetTypeStrength(w1.Type) * w1.Health / Warrior.DEFAULT_HEALTH;
			var str2 = GetTypeStrength(w2.Type) * w2.Health / Warrior.DEFAULT_HEALTH;
			var winProb12 = GetWinProbability(w1.Type, w2.Type);

			var attackCount1 = w1.Speed / w2.Speed;	// no. of attacks by 1st warrior.
			var attackCount2 = w2.Speed / w1.Speed;	// no. of attacks by 2nd warrior.
			var damage1 = Math.Max(w1.Attack - w2.Defence, 1) * attackCount1 * winProb12 * str1;
			var damage2 = Math.Max(w2.Attack - w1.Defence, 1) * attackCount2 * (1f - winProb12) * str2;
			w1.Health -= damage2;
			w2.Health -= damage1;

			var res = new BattleResult();
			res.Army1Casualties = damage2;
			res.Army2Casualties = damage1;
			res.Army1Wins = w1.Health > w2.Health;

			return res;
		}
	}
}